package com.vteba.utils.web;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.google.common.collect.Maps;
import com.vteba.utils.charstr.Char;
import com.vteba.utils.common.PropUtils;
import com.vteba.utils.json.FastJsonUtils;
import com.vteba.utils.json.JacksonUtils;

/**
 * Http Client工具类。包括普通请求，json请求，文件上传和下载等。
 * 
 * @author yinlei
 * @since 2014-8-14 18:04
 */
public class HttpUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtils.class);
    private static HttpHost httpHost = null;
    static {
        String hostName = PropUtils.get("host", "localhost");
        int port = PropUtils.getInt("port", 8080);
        String schema = PropUtils.get("scheme", "http");
        // 创建目标主机
        httpHost = new HttpHost(hostName, port, schema);
        //httpHost = new HttpHost("localhost", 8080, "http");
    }
    
    
    /**
     * http调用，返回结果类Bean。
     * @param params 参数值
     * @param urlPath 接口url
     * @return T
     */
    public static <T> T postBean(final Map<String, String> params, final String urlPath, final Class<T> clazz) {
        // 创建http post请求
        HttpPost httpPost = buildHttpPost(params, urlPath);
        // 发起调用，返回Json object
        T obj = resolveBean(clazz, httpPost);
        return obj;
    }
    
    /**
     * http调用返回一个JSONObject，可以做进一步的处理，取出来某一节点等。
     * @param params 参数值
     * @param urlPath 接口url
     * @return JSONObject
     */
    public static JSONObject postJsonObject(final Map<String, String> params, final String urlPath) {
        // 创建http post请求
        HttpPost httpPost = buildHttpPost(params, urlPath);
        // 发起调用，返回Json object
        JSONObject jsonObject = resolveJsonObject(httpPost);
        return jsonObject;
    }
    
    /**
     * http调用，返回结果数组
     * @param params 参数
     * @param urlPath 接口url
     * @return 结果 字节数组
     */
    public static byte[] postBytes(final Map<String, String> params, final String urlPath) {
        // 创建http post请求
        HttpPost httpPost = buildHttpPost(params, urlPath);
        // 发起调用
        byte[] result = resolve(httpPost);
        return result;
    }
    
    /**
     * 文件下载
     * @param params 参数
     * @param urlPath 路径
     * @return 文件字节数组
     */
    public static byte[] download(final Map<String, String> params, final String urlPath) {
        return postBytes(params, urlPath);
    }
    
    /**
     * 文件下载，记得关闭流。
     * @param params 参数
     * @param urlPath 路径
     * @return 文件流
     */
    public static InputStream downloadFile(final Map<String, String> params, final String urlPath) {
    	HttpPost httpPost = buildHttpPost(params, urlPath);
        InputStream result = resolveStream(httpPost);
        return result;
    }
    
    /**
     * 发起http post调用，返回List，参数是键值对的形式。
     * @param params 参数Map
     * @param urlPath 调用的接口名
     * @param resultClass 结果对象类
     * @return 结果List
     */
    public static <T> List<T> postList(final Map<String, String> params, final String urlPath, Class<T> resultClass) {
        // 创建http post请求
        HttpPost httpPost = buildHttpPost(params, urlPath);
        // 发起调用，处理请求结果
        List<T> result = resolveResult(resultClass, httpPost);
        return result;
    }
    
    /**
     * 发起http post调用，返回JavaBean。底层使用Jackson来实现的。可以改变属性的值，字段不一致也可以处理。
     * @param params 参数值
     * @param urlPath 接口url
     * @param resultClass 结果类型
     * @return &lt;T&gt; JavaBean实体
     */
    public static <T> T postForBean(final Map<String, String> params, final String urlPath, final Class<T> resultClass) {
        // 创建http post请求
        HttpPost httpPost = buildHttpPost(params, urlPath);
        // 发起调用，处理请求结果
        T result = resolveForBean(httpPost, resultClass);
        return result;
    }
    
    /**
     * 发起http post调用，返回List&lt;T&gt;。底层使用Jackson来实现的。可以改变属性的值，字段不一致也可以处理。
     * @param params 参数值
     * @param urlPath 接口url
     * @param resultClass 结果类型
     * @return List&lt;T&gt;
     */
    public static <T> List<T> postForList(final Map<String, String> params, final String urlPath, final Class<T> resultClass) {
        // 创建http post请求
        HttpPost httpPost = buildHttpPost(params, urlPath);
        // 发起调用，处理请求结果
        List<T> result = resolveForList(httpPost, resultClass);
        return result;
    }

    /**
     * 发起http请求，返回处理结果。
     * @param httpPost HttpPost
     * @param resultClass 结果类型
     * @return 结果List
     */
    private static <T> List<T> resolveForList(final HttpPost httpPost, final Class<T> resultClass) {
        CollectionType collectionType = JacksonUtils.constructCollectionType(List.class, resultClass);
        List<T> list = JacksonUtils.fromJson(resolve(httpPost), collectionType);
        return list;
    }
    
    /**
     * 发起http请求，返回处理结果。
     * @param httpPost HttpPost
     * @param resultClass 结果类型
     * @return 结果List
     */
    private static <T> T resolveForBean(final HttpPost httpPost, final Class<T> resultClass) {
        T entity = JacksonUtils.fromJson(resolve(httpPost), resultClass);
        return entity;
    }
    
    /**
     * 发起http请求，返回字节数组
     * @param httpPost HttpPost
     * @return byte[]
     */
    private static byte[] resolve(final HttpPost httpPost) {
        byte[] bytes = null;
        try {
            // 创建客户端
            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
            // 发起调用
            CloseableHttpResponse httpResponse = httpClient.execute(httpHost, httpPost);
            int status = httpResponse.getStatusLine().getStatusCode();
            if (status == 200) {// 结果正常
                bytes = EntityUtils.toByteArray(httpResponse.getEntity());
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("response code=[200] success，url=[{}]，byte size=[{}]", httpPost.getURI().toString(), bytes.length);
                }
            } else {// 结果异常
                LOGGER.error("请求的urlPath错误[{}]，响应码是[{}]", httpPost.getURI().toString(), status);
            }
            httpResponse.close();
            httpClient.close();
        } catch (ClientProtocolException e) {
            LOGGER.error("[{}]请求，客户端协议错误,msg=[{}].", httpPost.getURI().toString(), e.getMessage());
        } catch (IOException e) {
            LOGGER.error("[{}]请求IO错误,msg=[{}].", httpPost.getURI().toString(), e);
        }
        return bytes;
    }
    
    /**
     * 发起http请求，InputStream
     * @param httpPost HttpPost
     * @return InputStream
     */
    private static InputStream resolveStream(final HttpPost httpPost) {
        InputStream result = null;
        try {
            // 创建客户端
            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
            // 发起调用
            CloseableHttpResponse httpResponse = httpClient.execute(httpHost, httpPost);
            int status = httpResponse.getStatusLine().getStatusCode();
            if (status == 200) {// 结果正常
                result = httpResponse.getEntity().getContent();
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("response code=[200] success, url=[{}], stream size=[{}]", httpPost.getURI().toString(), result.available());
                }
            } else {// 结果异常
                LOGGER.error("请求的urlPath错误[{}]，响应码是[{}]", httpPost.getURI().toString(), status);
            }
            httpResponse.close();
            httpClient.close();
        } catch (ClientProtocolException e) {
            LOGGER.error("[{}]请求，客户端协议错误,msg=[{}].", httpPost.getURI().toString(), e.getMessage());
        } catch (IOException e) {
            LOGGER.error("[{}]请求IO错误,msg=[{}].", httpPost.getURI().toString(), e);
        }
        return result;
    }
    
    /**
     * 创建multipart entity，value为File或者String，日期最好格式化成String。
     * @param params 参数，key为参数名
     * @return HttpEntity
     */
    private static HttpEntity buildMultipartEntity(Map<String, Object> params) {
    	MultipartEntityBuilder builder = MultipartEntityBuilder.create();
    	builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    	
    	if (params != null) {
    		for (Entry<String, Object> entry : params.entrySet()) {
    			Object body = entry.getValue();
    			if (body instanceof File) {
    				File file = (File) body;
    				builder.addBinaryBody(entry.getKey(), file);
    			} else {
    				String text = body.toString();
    				builder.addTextBody(entry.getKey(), text, ContentType.create("text/plain", Char.UTF8));
    			}
    		}
    	}
    	
    	HttpEntity httpEntity = builder.build();
    	return httpEntity;
    }
    
    /**
     * 创建multipart entity，value为File或者String，日期最好格式化成String。
     * @param baseParams 普通文本参数，key为参数名
     * @param fileParams 文件参数，key为参数名
     * @return HttpEntity
     */
    private static HttpEntity buildMultipartEntity(Map<String, String> baseParams, Map<String, String> fileParams) {
    	MultipartEntityBuilder builder = MultipartEntityBuilder.create();
    	builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    	
    	if (baseParams != null) {
    		for (Entry<String, String> entry : baseParams.entrySet()) {
    			//StringBody body = new StringBody(entry.getValue(), ContentType.create("text/plain", Char.UTF8));
    			builder.addTextBody(entry.getKey(), entry.getValue(), ContentType.create("text/plain", Char.UTF8));
    		}
    	}
    	
    	if (fileParams != null) {
    		File file = null;
    		for (Entry<String, String> entry : fileParams.entrySet()) {
    			//FileBody fileBody = new FileBody(entry.getValue(), ContentType.DEFAULT_BINARY);
    			file = new File(entry.getValue());
    			builder.addBinaryBody(entry.getKey(), file);
    		}
    	}
    	
    	HttpEntity httpEntity = builder.build();
    	return httpEntity;
    }
    
    /**
     * 创建multipart entity，value为File
     * @param fileParams 参数，key为参数名
     * @return HttpEntity
     */
    private static HttpEntity buildFileEntity(Map<String, File> fileParams) {
    	MultipartEntityBuilder builder = MultipartEntityBuilder.create();
    	builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    	
    	if (fileParams != null) {
    		for (Entry<String, File> entry : fileParams.entrySet()) {
    			builder.addBinaryBody(entry.getKey(), entry.getValue());
    		}
    	}
    	
    	HttpEntity httpEntity = builder.build();
    	return httpEntity;
    }
    
    /**
     * 构建http multipart post请求，用于上传文件。
     * @param params 请求参数
     * @param urlPath 请求url地址
     * @return HttpPost实例
     */
    private static HttpPost buildFilePost(final Map<String, File> params, final String urlPath) {
        HttpEntity httpEntity = buildFileEntity(params);
        
        // 设置post请求
        HttpPost httpPost = new HttpPost(urlPath);
        // 设置post请求体
        httpPost.setEntity(httpEntity);
        // 请求连接设置
        RequestConfig config = RequestConfig.custom().setSocketTimeout(6000).setConnectTimeout(6000).build();
        httpPost.setConfig(config);
        return httpPost;
    }
    
    /**
     * 构建http multipart post请求，用于上传文件。
     * @param params 请求参数
     * @param urlPath 请求url地址
     * @return HttpPost实例
     */
    private static HttpPost buildMultipartPost(final Map<String, Object> params, final String urlPath) {
        HttpEntity httpEntity = buildMultipartEntity(params);
        
        // 设置post请求
        HttpPost httpPost = new HttpPost(urlPath);
        // 设置post请求体
        httpPost.setEntity(httpEntity);
        // 请求连接设置
        RequestConfig config = RequestConfig.custom().setSocketTimeout(6000).setConnectTimeout(6000).build();
        httpPost.setConfig(config);
        return httpPost;
    }
    
    /**
     * 构建http multipart post请求，用于上传文件。
     * @param baseParams 基本String参数，日期类要格式化成String
     * @param fileParams 文件参数，value为文件路径
     * @param urlPath 请求url地址
     * @return HttpPost实例
     */
    private static HttpPost buildMultipartPost(final Map<String, String> baseParams, final Map<String, String> fileParams, final String urlPath) {
        HttpEntity httpEntity = buildMultipartEntity(baseParams, fileParams);
        
        // 设置post请求
        HttpPost httpPost = new HttpPost(urlPath);
        // 设置post请求体
        httpPost.setEntity(httpEntity);
        // 请求连接设置
        RequestConfig config = RequestConfig.custom().setSocketTimeout(6000).setConnectTimeout(6000).build();
        httpPost.setConfig(config);
        return httpPost;
    }
    
    /**
     * 上传文件，以及普通参数。
     * @param params 请求参数，value可以为File，可以为普通String参数。
     * @param urlPath 请求url地址
     * @return 返回结果
     */
    public static byte[] uploadFile(final Map<String, Object> params, final String urlPath) {
    	return resolve(buildMultipartPost(params, urlPath));
    }
    
    /**
     * 上传文件，以及普通参数。
     * @param params 请求参数，value可以为File，可以为普通String参数。
     * @param urlPath 请求url地址
     * @param resultClass 返回值泛型类
     * @return 返回结果bean
     */
    public static <T> T uploadFile(final Map<String, Object> params, final String urlPath, final Class<T> resultClass) {
    	byte[] result = resolve(buildMultipartPost(params, urlPath));
    	if (result == null) {
    		return null;
    	}
    	T t = FastJsonUtils.fromJson(result, resultClass);
    	return t;
    }
    
    /**
     * 上传文件，以及普通参数。key为参数名，value为参数值
     * @param params 请求参数，value为普通String参数。
     * @param fileParams value为File参数
     * @param urlPath 请求url地址
     * @return 返回结果
     */
    public static byte[] uploadFile(final Map<String, String> params, final Map<String, String> fileParams, final String urlPath) {
    	return resolve(buildMultipartPost(params, fileParams, urlPath));
    }
    
    /**
     * 上传文件，以及普通参数。key为参数名，value为参数值
     * @param params 请求参数，value为普通String参数。
     * @param fileParams value为File参数
     * @param urlPath 请求url地址
     * @param resultClass 返回值泛型类
     * @return 返回结果bean
     */
    public static <T> T uploadFile(final Map<String, String> params, final Map<String, String> fileParams, final String urlPath, final Class<T> resultClass) {
    	byte[] result = resolve(buildMultipartPost(params, fileParams, urlPath));
    	if (result == null) {
    		return null;
    	}
    	T t = FastJsonUtils.fromJson(result, resultClass);
    	return t;
    }
    
    /**
     * 根据文件路径，上传文件。
     * @param params 请求参数，value文件路径
     * @param urlPath 请求url地址
     * @return 返回结果
     */
    public static byte[] fileUploads(final Map<String, String> params, final String urlPath) {
    	return resolve(buildMultipartPost(null, params, urlPath));
    }
    
    /**
     * 根据文件路径，上传文件。
     * @param params 请求参数，value文件路径
     * @param urlPath 请求url地址
     * @param resultClass 返回值泛型类
     * @return 返回结果
     */
    public static <T> T fileUploads(final Map<String, String> params, final String urlPath, final Class<T> resultClass) {
    	byte[] result = resolve(buildMultipartPost(null, params, urlPath));
    	if (result == null) {
    		return null;
    	}
    	return FastJsonUtils.fromJson(result, resultClass);
    }
    
    /**
     * 上传文件。
     * @param params 请求参数，value为文件
     * @param urlPath 请求url地址
     * @return 返回结果
     */
    public static byte[] fileUpload(final Map<String, File> params, final String urlPath) {
    	byte[] result = resolve(buildFilePost(params, urlPath));
    	return result;
    }
    
    /**
     * 上传文件。
     * @param params 请求参数，value为文件
     * @param urlPath 请求url地址
     * @param resultClass 返回值泛型类
     * @return 返回结果
     */
    public static <T> T fileUpload(final Map<String, File> params, final String urlPath, final Class<T> resultClass) {
    	byte[] result = resolve(buildFilePost(params, urlPath));
    	if (result == null) {
    		return null;
    	}
    	return FastJsonUtils.fromJson(result, resultClass);
    }
    
    /**
     * 根据请求参数和请求url地址，构建http post请求。
     * @param params 请求参数
     * @param urlPath 请求url地址
     * @return HttpPost实例
     */
    private static HttpPost buildHttpPost(final Map<String, String> params, final String urlPath) {
        // 构建请求uri
        URI uri = null;
        try {
            URIBuilder uriBuilder = new URIBuilder();
            // 设置参数
            if (params != null) {
            	for (Entry<String, String> entry : params.entrySet()) {
            		uriBuilder.setParameter(entry.getKey(), entry.getValue());
            	}
            }
            // 设置请求的路径
            uriBuilder.setPath(urlPath);
            uri = uriBuilder.build();
        } catch (URISyntaxException e1) {
            LOGGER.error("HttpClient创建URI错误。可能传递的参数错误。", e1.getMessage());
            return null;
        }
        
        // 设置post请求
        HttpPost httpPost = new HttpPost(uri);
        // 设置post请求体
        ///重复了
//        List<NameValuePair> list = URLEncodedUtils.parse(uri, "UTF-8");
//        try {
//            UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(list, "UTF-8");
//            httpPost.setEntity(urlEncodedFormEntity);
//        } catch (UnsupportedEncodingException e) {
//            LOGGER.error("设置http请求体参数错误，不支持的编码。", e.getMessage());
//        }
        RequestConfig config = RequestConfig.custom().setSocketTimeout(6000).setConnectTimeout(6000).build();
        httpPost.setConfig(config);
        return httpPost;
    }
    
    /**
     * 创建以json为参数的httpPost
     * @param params 参数
     * @param urlPath url路径
     * @return HttpPost
     */
    private static HttpPost buildJsonHttpPost(final Object params, final String urlPath) {
        // 设置post请求
        HttpPost httpPost = new HttpPost(urlPath);
        ByteArrayEntity entity = new ByteArrayEntity(FastJsonUtils.toJsonBytes(params), ContentType.APPLICATION_JSON);
        httpPost.setEntity(entity);
        
        // 超时配置
        RequestConfig config = RequestConfig.custom().setSocketTimeout(6000).setConnectTimeout(6000).build();
        httpPost.setConfig(config);
        return httpPost;
    }
    
    /**
     * 发起http请求，返回处理结果。
     * @param resultClass 结果类型
     * @param httpPost HttpClient
     * @return 结果List
     */
    private static <T> List<T> resolveResult(final Class<T> resultClass, final HttpPost httpPost) {
        List<T> object = FastJsonUtils.fromJsonArray(resolve(httpPost), resultClass);
        return object;
    }

    /**
     * 发起http请求，返回处理结果。
     * @param resultClass 结果类型
     * @param httpPost HttpClient
     * @return 结果List
     */
    private static <T> T resolveBean(final Class<T> resultClass, final HttpPost httpPost) {
        T object = FastJsonUtils.fromJson(resolve(httpPost), resultClass);
        return object;
    }
    
    /**
     * 处理返回JSONObject的调用。统一返回JSONObject，然后需要特殊的处理都可以从这里获取。
     * 包括获取某一节点的值，以及某一节点JSONArray等等。
     * @param httpPost HttpPost实例
     * @return JSONObject
     */
    private static JSONObject resolveJsonObject(final HttpPost httpPost) {
        String json = new String(resolve(httpPost), Char.UTF8);
        return JSON.parseObject(json);
    }
    
    /**
     * 发起post请求，参数json格式，返回结果也是json格式
     * @param params 参数
     * @param urlPath url路径
     * @param resultClass 返回结果类
     * @return 请求结果
     */
    public <T> T postJson(final Object params, final String urlPath, Class<T> resultClass) {
    	HttpPost post = buildJsonHttpPost(params, urlPath);
    	byte[] bytes = resolve(post);
    	return FastJsonUtils.fromJson(bytes, resultClass);
    }
    
    /**
     * 发起post请求，参数json格式，返回结果是json array格式
     * @param params 参数
     * @param urlPath url路径
     * @param resultClass 返回结果类
     * @return 请求结果
     */
    public <T> List<T> postJsonList(final Object params, final String urlPath, Class<T> resultClass) {
    	HttpPost post = buildJsonHttpPost(params, urlPath);
    	byte[] bytes = resolve(post);
    	return FastJsonUtils.fromJsonArray(bytes, resultClass);
    }
    
    public static void main(String[] aa) {
    	Map<String, String> data = new HashMap<>();
    	data.put("tokenId", "aa");
    	data.put("orderId", "bb");
    	data.put("type", "1");
    	
    	Map<String, String> fileParams = new HashMap<>();
    	fileParams.put("firstFile", "C:\\Users\\yinlei\\Pictures\\2015090410222952700.jpg");
    	fileParams.put("secondFile", "C:\\Users\\yinlei\\Pictures\\2015090410222952700.jpg");
    	
    	String url = "/upload";
    	uploadFile(data, fileParams, url);
    	
    	Map<String, Object> map = Maps.newHashMap();
    	map.put("tokenId", "aa");
    	map.put("orderId", "bb");
    	map.put("type", "1");
    	map.put("firstFile", new File("C:\\Users\\yinlei\\Pictures\\2015090410222952700.jpg"));
    	map.put("secondFile", new File("C:\\Users\\yinlei\\Pictures\\2015090410222952700.jpg"));
    	uploadFile(map, url);
    	
    	fileUploads(fileParams, url);
    }
}

